/*
 * Copyright (c) 2011, Khaki Young <nssprite@gmail.com>
 * All rights reserved.
 *
 * THIS SOFTWARE IS UNDER THE GPL LICENSE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <getopt.h>
#include <time.h>
#include <pthread.h>
#include "coralhttpd.h"

#define	CORALHTTP_NAME	"CoralHttp"
#define CORALHTTP_VERSION "0.1.1"
#define SERVER_NAME	"CoralHttp/0.1.1"	
#define CORALHTTP_OK	0
#define CORALHTTP_ERR	-1
#define BUFSIZE	1024*4

#define CORALHTTP_PORT	8910
#define HTTPD_CONFIG "httpd.conf"

/* Log levels */
#define CORALHTTP_DEBUG 0
#define CORALHTTP_VERBOSE 1
#define CORALHTTP_NOTICE 2
#define CORALHTTP_WARNING 3

struct settings settings; /* Server global state */

char *response_format = "HTTP/1.1 %d %s\r\n"
					"Content-Type: text/html\r\n"
					"Content-Length: %d\r\n\r\n"
					"%s";

char *not_found_format = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
					"<HTML><HEAD>"
					"<TITLE>404 Not Found</TITLE>"
					"</HEAD><BODY>"
					"<H1>Not Found</H1>"
					"The requested URL %s was not found on this server.<P>"
					"<HR>"
					"<ADDRESS>%s Server at localhost Port %d</ADDRESS>"
					"</BODY></HTML>";
char *bad_request = 
	"HTTP/1.1 400 Bad Request\r\n"
	"Content-Type: text/html\r\n"
	"Content-Length: 39\r\n\r\n"
	"<h1>Bad Request (Invalid Hostname)</h1>";

static void initServer() {
	//
}

static char *getLocaltime() {
	time_t timep;
	struct tm *p;
	time(&timep);
	p = localtime(&timep);
	//char local_time[80];
	char *local_time = (char *)malloc(80);
	sprintf(local_time, "%d/%d/%d %d:%02d:%02d", (1900 + p->tm_year), (1 + p->tm_mon), p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
	return local_time;
}

/* Log action */
static void corallog(const char *type, const char *msg) {
	//char buf[20] = settings.logpath;
	char buf[20] = "logs";
	int len = strlen(type);
	if (type[len - 1] == "/") {
		strcat(buf, type);
	} else {
		strcat(buf, "/");
		strcat(buf, type);
	}
	strcat(buf, ".log");
	FILE *fp = fopen(buf, "a+");
	if (fp == NULL) exit(-1);
	char *local_time = getLocaltime();
	fprintf(fp, "[%s] %s\n", local_time, msg);
	fclose(fp);
}

static void record(const char *msg) {
	FILE *fp = fopen("/tmp/record.txt", "w+");
	if (fp == NULL) exit(CORALHTTP_ERR);
	//char *local_time = getLocalTime();
	fprintf(fp, "%s\n", msg);
	fclose(fp);
}

/* Init server config */
static int initServerConfig() {
	//char buf[80];
	char *p, *tmp;
	int port;
	FILE *fp = fopen(HTTPD_CONFIG, "r");
	if (fp == NULL) {
		corallog("error", "Fail to open httpd.conf");
		return -1;
	}
	while (!feof(fp)) 
	{
		//memset(buf, 0, 80);
		char buf[80] = {'\0'};
		fgets(buf, sizeof(buf), fp);
		// get port's config
		if (strstr(buf, "port") == buf) {
			if ((p = strchr(buf, '=')) == NULL) {
				corallog("error", "error in httpd.conf");
				return -1;
			} else {
				port = atoi(p + 2);
			}
			if (port <= 0) {
				corallog("error", "error port");
				return -1;
			}
			settings.port = port;
		}
		// get daemonize's config
		if (strstr(buf, "daemonize") == buf) {
			if ((p = strchr(buf, '=')) == NULL) {
				corallog("error", "error in httpd.conf");
				return -1;
			} else {
				p += 2;
			}
			if (strncasecmp(p, "yes", 3) == 0) {
				settings.daemonize = 1;
			} else if (strncasecmp(p, "no", 2) == 0) {
				settings.daemonize = 0;
			} else {
				corallog("error", "error daemonize config");
				return -1;
			}
		}
		// get the document root
		if (strstr(buf, "root") == buf) {
			if ((p = strchr(buf, '=')) == NULL) {
				corallog("error", "error in httpd.conf");
				return -1;
			} else {
				p += 2;
			}
			settings.root = p;
		}
	}
	fclose(fp);

	//printf("come here");
	//printf("%s", settings.root);
	//printf("%d", settings.port);
	settings.root = "/home/khaki/Documents/develop/php";
	settings.backlog = 5;
	settings.pidfile = "/tmp/coralhttp.pid";
	settings.logpath = "/tmp";
	return 0;
}

/* Analysis the Http request header */
static char *head_analysis(char *p) {
	char *ptmp = p;
	if (strncmp(ptmp, "GET /", 5) == 0) {
		ptmp += 5;
		while (*ptmp != ' ') {
			ptmp++;
		}
		*ptmp++ = '\0';

		if (strncmp(ptmp, "HTTP/", 5) == 0) {
			return p + 5;
		} else {
			return NULL;
		}
	} else {
		return NULL;
	}
}

/* Return the body of the php's content */
static char *php_request(char *content, int len) {
	char *p = content;
	char *end = p + len -3;
	for(; p < end; p++) {
		if (*p == '\r' && *(p+1) == '\n' && *(p+2) == '\r' && *(p+3) == '\n') return p+4;
	}

	return p;
}

static int read_send(char *head, char *requestfile, int connfd) {
	size_t rdLen;
	char *pbuf[BUFSIZE];
	if (access(requestfile, R_OK) == 0) {

		if (strcasecmp(&requestfile[strlen(requestfile) - 3], "php") == 0) {
			//record("php");
			char cmd[1024] = {'\0'};
			sprintf(cmd, "php-cgi %s > /tmp/php-swap", requestfile);
			system(cmd);
			char phpContent[1024] = {'\0'};
			FILE *fp = fopen("/tmp/php-swap", "r");
			int len = fread(phpContent, 1024, 1, fp);
			fclose(fp);

			char *phpBody = php_request(phpContent, strlen(phpContent));
			char response[1024] = {'\0'};
			sprintf(response, response_format, 200, "OK", strlen(phpBody), phpBody);
			send(connfd, response, strlen(response), 0);
			close(connfd);
			return 0;
		}

		int fd;
		fd = open(requestfile, O_RDONLY);
		send(connfd, head, strlen(head), 0);
		while ((rdLen = read(fd, pbuf, BUFSIZE)) > 0) {
			send(connfd, pbuf, rdLen, 0);
		}
		close(fd);
	} else {
		char body[1024] = {'\0'};
		char response[1024] = {'\0'};
		sprintf(body, not_found_format, requestfile, SERVER_NAME, settings.port);
		sprintf(response, response_format, 404, "NOT FOUND", strlen(body), body);
		send(connfd, response, strlen(response), 0);
	}
	close(connfd);

	return 0;
}

static char *get_content_type(char *path) {
	int n = strlen(path);
	char *ctype = NULL;
	if (strcasecmp(&path[n-4], "jpeg") == 0 || strcasecmp(&path[n-3], "jpg") == 0) {
		ctype = "image/jpeg";
	} else if (strcasecmp(&path[n-3], "gif") == 0) {
		ctype = "image/gif";
	} else if (strcasecmp(&path[n-3], "png") == 0) {
		ctype = "image/png";
	} else if (strcasecmp(&path[n-2], "js") == 0) {
		ctype = "application/x-javascript";
	} else if (strcasecmp(&path[n-3], "css") == 0) {
		ctype = "text/css";
	} else if (strcasecmp(&path[n-4], "json") == 0) {
		ctype = "application/json";
	} else if (strcasecmp(&path[n-3], "php") == 0) {
		ctype = "text/html";
	} else {
		ctype = "text/html";
	}
	return ctype;
}

static void *thr_http(int connfd) {
	time_t timep;
	struct tm *p;
	time(&timep);
	p = localtime(&timep);
	char *wday[] = {"Sun", "Mon", "Tue", "Wen", "Thu", "Fri", "Sat"};
	char *ymon[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Set", "Oct", "Nov", "Dec"};
	char buf[1024];
	char rcvbuf[1024];
	char *path = NULL;
	if (recv(connfd, rcvbuf, sizeof(rcvbuf), 0) > 0) {
		path = head_analysis(rcvbuf);
		corallog("access", path);
	}
	//char *requestfile = (char *)malloc(30);

	char requestfile[1024];
	sprintf(requestfile, "%s/%s", settings.root, path);
	//corallog("access", requestfile);
	char *ctype = get_content_type(path);
	sprintf(buf, "HTTP/1.1 200 OK\r\nServer: CoralHttp/0.1.1\r\nDate: %s, %d %s %d %02d:%02d:%02d GMT\r\nContent-Type:%s;charset=utf-8\r\n\r\n", 
			wday[p->tm_wday], p->tm_mday, ymon[p->tm_mon], (1900 + p->tm_year), p->tm_hour, p->tm_min, p->tm_sec, ctype);
	read_send(buf, requestfile, connfd);
}

static void daemonize() {
	pid_t pid = fork();
	
	if (pid != 0) {
		exit(1);
	}
	setsid();

	FILE *pid_fp;
	pid_fp = fopen(settings.pidfile, "w+");
	if (pid_fp) {
		fprintf(pid_fp, "%d\n", getpid());
		fclose(pid_fp);
	}
}

static int http_start() {
	int sockfd;
	struct sockaddr_in serverAddr;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		close(sockfd);
		//
		return -1;
	}

	bzero(&serverAddr, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(settings.port);
	serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr)) == -1) {
		close(sockfd);	
		//
		return -2;
	}

	if (listen(sockfd, settings.backlog) == -1) {
		close(sockfd);	
		//
		return -3;
	}

	while (1) {
		struct sockaddr_in cliAddr;
		size_t cliAddrLen = sizeof(cliAddr);
		pthread_t ntid;
		int err;
		char from[20],
			 rcvbuf[1024];
		int connfd = accept(sockfd, (struct sockaddr *)&cliAddr, &cliAddrLen);
		err = pthread_create(&ntid, NULL, thr_http, connfd);
	
		//thr_http(connfd);
		if (err != 0) {
			fprintf(stderr, "Can't create thread: %s\n", strerror(err));
		}
	}
}

static void kill_signal_master(const int sig) {
	remove(settings.pidfile);
	kill(0, SIGTERM);

	exit(0);
}

static void kill_signal_worker(const int sig) {
	//
	exit(0);
}

static void version() {
	printf("CoralHttp server version is %s\n", CORALHTTP_VERSION);
	exit(0);
}

static void show_help() {
	char *help = "--------------------CoralHttp---------------------\n"
				 "\tstart	start the http server.\n"
				 "\tstop	stop the http server.\n"
				 "\t-h	print this help and exit.\n\n"
				 "Usage:\n"
				 "\t./coralhttpd start|stop|restart\n";
	fprintf(stderr, help, sizeof(help));
	exit(0);
}

int main(int argc, char *argv[])
{
	initServerConfig();

	if (argc == 2) {
		if (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0) {
			version();
		}
		if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
			show_help();
		}
		/*
		if (strcmp(argv[1], "start") == 0) {
			http_start();
		}
		if (strcmp(argv[1], "restart") == 0) {
			//
		}
		if (strcmp(argv[1], "stop") == 0) {
			//
		}
		*/
	} 

	if (settings.daemonize) {
		daemonize();
	}
	
	http_start();

	return 0;
}
